/***************************************************************************
 *                         CHM hhc file parser                             *
 *                           -------------------                           *
 *                                                                         *
 *  author:     Nestle <zhurx4g@gmail.com>                                 *
 *  notes:                                                                 *
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Lesser General Public License as        *
 *   published by the Free Software Foundation; either version 2.1 of the  *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 ***************************************************************************/

#include "qchm.h"
#include <QFile>
#include <QDataStream>
#include <QTextCodec>
#include <QMessageBox>
#include "chm_lib.h"
#include "QHHCParser.h"
#include "qdefaulthhchandler.h"

struct extract_context
{
    QString base_path;
    QString* hhcPath;
    QString* hhkPath;
    QChm* chmObject;
};

/*
 * callback function for enumerate API
 */
int _extract_callback(struct chmFile *h,
              struct chmUnitInfo *ui,
              void *context)
{
    char buffer[32768];
    struct extract_context *ctx = (struct extract_context *)context;

    QString fileFullName(ctx->base_path);
    fileFullName.append(QTextCodec::codecForName("UTF-8")->toUnicode(ui->path));

    QString filePath(QTextCodec::codecForName("UTF-8")->toUnicode(ui->path));
    if(filePath.endsWith(".hhc",Qt::CaseInsensitive)){
        ctx->hhcPath->append(filePath.right(filePath.length()-1));
    }
    if(filePath.endsWith(".hhk",Qt::CaseInsensitive)){
        ctx->hhkPath->append(filePath.right(filePath.length()-1));
    }

    if(filePath.startsWith("/$",Qt::CaseInsensitive)||filePath.startsWith("/#",Qt::CaseInsensitive)||filePath.startsWith(":",Qt::CaseInsensitive)||filePath.startsWith("/{",Qt::CaseInsensitive)){
        return CHM_ENUMERATOR_CONTINUE;
    }
    emit ctx->chmObject->dontcallme(QString("Extract File: ")+filePath);
    if(filePath.endsWith('/')||filePath.endsWith('\\')){
        QDir dir;
        QString apth(ctx->base_path);
        apth.append(filePath);
        dir.mkpath(apth);        
        return CHM_ENUMERATOR_CONTINUE;
    }
    if (ui->path[0] != '/')
        return CHM_ENUMERATOR_CONTINUE;

    /* quick hack for security hole mentioned by Sven Tantau */
    if (strstr(ui->path, "/../") != NULL)
    {
        return CHM_ENUMERATOR_CONTINUE;
    }

    if (ui->length != 0)
    {
        FILE *fout;
        LONGINT64 len, remain=ui->length;
        LONGUINT64 offset = 0;

        printf("--> %s\n", ui->path);
        if ((fout = fopen(fileFullName.toLocal8Bit().data(), "wb")) == NULL)
        {
              return CHM_ENUMERATOR_FAILURE;
        }

        while (remain != 0)
        {
            len = chm_retrieve_object(h, ui, (unsigned char *)buffer, offset, 32768);
            if (len > 0)
            {
                fwrite(buffer, 1, (size_t)len, fout);
                offset += len;
                remain -= len;
            }
            else
            {
                qWarning("incomplete file: %s\n", ui->path);
                break;
            }
        }

        fclose(fout);
    }
    return CHM_ENUMERATOR_CONTINUE;
}

bool doExact(void* chmFile,int c, QString* v,QString* hhcPath,QString* hhkPath,QChm* chm)
{
    QString lg;
    struct chmFile *h;
    struct extract_context ec;
    ec.hhcPath = hhcPath;
    ec.hhkPath = hhkPath;
    ec.chmObject = chm;


    if (c < 3)
    {
        qWarning("usage: %s <chmfile> <outdir>\n", v[0].toLocal8Bit().data());
        return false;
    }
    h = (struct chmFile*)chmFile;
    if (h == NULL)
    {
        return false;
    }
    qWarning("%s:\n", v[1].toLocal8Bit().data());
    ec.base_path = v[2].toLocal8Bit().data();
    if (! chm_enumerate(h,
                        CHM_ENUMERATE_ALL,
                        _extract_callback,
                        (void *)&ec)){
        qWarning("   *** ERROR ***\n");
        return false;
    }

    return true;
}

int getChmList(struct chmFile *h,
              struct chmUnitInfo *ui,
              void *context)
{
    QString filePath(ui->path);
    if(filePath.startsWith("/$",Qt::CaseInsensitive)||filePath.startsWith("/#",Qt::CaseInsensitive)||filePath.startsWith(":",Qt::CaseInsensitive)||filePath.startsWith("/{",Qt::CaseInsensitive)){
        return CHM_ENUMERATOR_CONTINUE;
    }
    QStringList* list = (QStringList*)context;
    list->append(ui->path);
    return CHM_ENUMERATOR_CONTINUE;
}
QChm::QChm(const QString& fileName):_fileName(fileName)
{
    processdCount = 0;
}
QChm::~QChm()
{
    close();
}
bool QChm::open()
{
    _chmFile = chm_open(_fileName.toLocal8Bit().data());
    return _chmFile!=NULL;
}
void QChm::close()
{
    if((chmFile*)_chmFile!=NULL){
        chm_close((chmFile*)_chmFile);
        _chmFile = NULL;
    }
}
const QStringList& QChm::enumerateDirList(QString prefix)
{
    if(isOpen()){
        if (! chm_enumerate_dir((chmFile*)_chmFile,prefix.toLocal8Bit().data(),CHM_ENUMERATE_ALL,getChmList,&dirList))
            dirList.clear();
    }else{
        dirList.clear();
    }
    return dirList;
}
const QStringList& QChm::enumerateFileList()
{
    if(isOpen()){
        if (! chm_enumerate((chmFile*)_chmFile,CHM_ENUMERATE_ALL,getChmList,&fileList))
            fileList.clear();
    }else{
        fileList.clear();
    }
    return fileList;
}
const ChmEntityInfo& QChm::getEntityInfo(const QString& url)
{
    chmUnitInfo ui;
    if(isOpen()&&(chm_resolve_object((chmFile*)_chmFile, url.toLocal8Bit().data(), &ui) == CHM_RESOLVE_SUCCESS)){
        unitInfo.start=ui.start;
        unitInfo.length=ui.length;
        unitInfo.space=ui.space;
        unitInfo.flags=ui.flags;
        for(int i=0;i<(CHM_MAX_PATHLEN+1);i++){
            unitInfo.path[i]=ui.path[i];
        }
    }else{
        unitInfo.start=0;
        unitInfo.length=0;
        unitInfo.space=0;
        unitInfo.flags=0;
        unitInfo.path[0]='\0';
    }
    return unitInfo;
}
const QByteArray& QChm::getEntityContent(const QString& url)
{
    return content;
}
void QChm::setParam(int paramType,int value)
{
    chm_set_param((chmFile*)_chmFile,paramType,value);
}
bool QChm::isOpen()
{
    return (chmFile*)_chmFile!=NULL;
}
void QChm::getTopicsByHandler(QHHCHandler& handler)
{
    QHHCParser parser;
    parser.parse(this->_hhcFileName,&handler);
}
/**
*
*/
bool QChm::unzip(QString dirName){
    QString arguments[3];

    QString chmFile = _fileName;
    chmFile = chmFile.replace("/","\\");

    QString dirFName = dirName;
    dirFName = dirFName.replace("/","\\");

    arguments[0] = "";
    arguments[1] = _fileName;
    arguments[2] = dirName;
    processdCount =0;
    return doExact(_chmFile,3,arguments,&_hhcFileName,&_hhkFileName,this);
}
